LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

RAGFlow工具技术文档

2025/3/7 AI

RAGFlow代理组件的核心

  • 集成外部数据源:通过检索模块来继承外部知识库、数据库、文档等内容,增强模型的回答能力
  • 生成式任务处理:利用模型生成的能力,基于用户的查询返回自然语言的回答
  • 模块化:RAGFlow支持灵活的模块化设计,可以通过配置和集成不同的组件来满足特定的需求

RAGFlow代理组件

RAGFlow 的代理组件是其架构中的核心部分,负责管理与外部数据源生成模型的交互。代理组件不仅可以从不同的外部数据源中获取知识,还能将检索到的信息与生成模型结合,从而生成更加准确的回答。

代理组件的功能

  • 数据检索:代理组件会从多个外部数据源(如数据库、文档、API等)进行数据检索,获取与用户查询相关的信息。
  • 生成模型的调用:将检索到的信息与生成模型结合,通过生成模型提供一个流畅且准确的答案。
  • 多源数据融合:在生成回答时,代理组件能够处理多来源的数据,并将其融合成最终的答案。

组件 → web/src/utils *待定

组件前端交互中心web/src/utils/api.ts
它定义了一些与“画布”相关的 API 接口地址,用于与后端进行通信。通过这些接口,前端应用可以进行不同操作,如获取模板、操作画布、调试等

listTemplates: 用于获取所有画布模板的列表,发送请求到 /canvas/templates
listCanvas: 获取画布列表的接口,发送请求到 /canvas/list

Begin组件

Begin 组件设置开场问候语或接受用户的输入。当您创建代理时,它会自动填充到画布上,无论是从模板还是从头开始(从空白模板)。工作流程中应该只有一个 Begin 组件。

api/ragflow/web/src/locales/zh.ts
……
setAnOpenerInitial: 你好! 我是你的助理,有什么可以帮到你的吗?,

静态消息: /v1/canvas/set
生成问答、问题优化、问题类型、关键词/v1/llm/list
知识检索/v1/llm/list + /v1/kb/list

以**问题优化 **(RewriteQuestion:MoodyCrabsPeel) 为例:


后端内容

知识检索Retrieval 路径→agent/component/retrieval.py

# 记录日志
import logging
# 抽象基类模块
from abc import ABC
# 用于数据处理,尤其是处理数据框(DataFrame)
import pandas as pd
# 从数据库相关模块导入 LLMType,这个可能是用来定义 LLM(语言模型)类型的枚举
from api.db import LLMType
# 从数据库服务模块中导入,分别用于知识库服务和 LLM 配置的封装
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMBundle
# 加载应用程序的设置
from api import settings
# 基础组件类,用于构建此组件的父类
from agent.component.base import ComponentBase, ComponentParamBase
# 从标签模块中导入,用于对查询进行标记
from rag.app.tag import label_question


class RetrievalParam(ComponentParamBase):

    """
    Define the Retrieval component parameters.
    """
    def __init__(self):
        super().__init__()
        # 相似度阈值
        self.similarity_threshold = 0.2
        # 关键字相似度的权重
        self.keywords_similarity_weight = 0.5
        # 检索时返回的结果数
        self.top_n = 8
        # 在检索时考虑的最大数量
        self.top_k = 1024
        # 知识库的 ID 列表
        self.kb_ids = []
        # 重新排序模型的 ID (可能为空)
        self.rerank_id = ""
        # 当没有找到任何结果时的响应文本
        self.empty_response = ""
#### check_decimal_float???
   # check():对参数进行验证,确保相似度阈值和权重为有效的小数,且 top_n 是正数。
    def check(self):
        # "相似性阈值"
        self.check_decimal_float(self.similarity_threshold, "[Retrieval] Similarity threshold")
        # "关键词相似度权重"
        self.check_decimal_float(self.keywords_similarity_weight, "[Retrieval] Keyword similarity weight")
        self.check_positive_number(self.top_n, "[Retrieval] Top N")

# Retrieval 类继承自 ComponentBase 和 ABC(抽象基类)
# 表示这是一个组件,并实现了抽象方法
class Retrieval(ComponentBase, ABC):
    component_name = "Retrieval"

##### history, **kwargs???
    # _run为核心方法 
# query输入查询内容 包括'content'键 提取第一个内容项 将查询内容转换为字符串
    def _run(self, history, **kwargs):
        query = self.get_input()
        query = str(query["content"][0]) if "content" in query else ""
    # 从知识库服务中获取指定 ID 的知识库。如果没有找到相关的知识库,返回空的响应。
        kbs = KnowledgebaseService.get_by_ids(self._param.kb_ids)
        if not kbs:
            return Retrieval.be_output("")
# 获取所有知识库的嵌入模型 ID(embd_id),确保所有知识库使用相同的嵌入模型。
# 如果使用的嵌入模型不一致,抛出异常
        embd_nms = list(set([kb.embd_id for kb in kbs]))
        assert len(embd_nms) == 1, "Knowledge bases use different embedding models."

##### LLMBundle???
# 创建 LLMBundle 实例来封装嵌入模型,使用画布的租户 ID 和嵌入模型的 ID 配置嵌入模型,并设置到画布
        embd_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.EMBEDDING, embd_nms[0])
        self._canvas.set_embedding_model(embd_nms[0])

   # 若提供了 rerank_id,则加载重新排序的模型
        rerank_mdl = None
        if self._param.rerank_id:
            rerank_mdl = LLMBundle(kbs[0].tenant_id, LLMType.RERANK, self._param.rerank_id)

  # 使用 settings.retrievaler.retrieval 方法执行知识检索操作,传入查询和各种配置参数
# query:用户查询   embd_mdl:嵌入模型    其他参数包括知识库 ID、相似度阈值、关键词相似度权重等
        kbinfos = settings.retrievaler.retrieval(query, embd_mdl, kbs[0].tenant_id, self._param.kb_ids,
                                        1, self._param.top_n,
                                        self._param.similarity_threshold, 1 - self._param.keywords_similarity_weight,
                                        aggs=False, rerank_mdl=rerank_mdl,
                                        rank_feature=label_question(query, kbs))
# 如果检索结果为空(没有找到相关内容),则返回空响应
# 如果设置了 empty_response,则在响应中返回该内容。
        if not kbinfos["chunks"]:
            df = Retrieval.be_output("")
            if self._param.empty_response and self._param.empty_response.strip():
                df["empty_response"] = self._param.empty_response
            return df
'''
如果有检索结果,将结果转换为 Pandas 的 DataFrame 格式。
将 content_with_weight 重命名为 content,然后删除 content_with_weight 列。
输出调试日志,记录查询和检索结果。
'''
        df = pd.DataFrame(kbinfos["chunks"])
        df["content"] = df["content_with_weight"]
        del df["content_with_weight"]
        logging.debug("{} {}".format(query, df))
        return df

如果你需要一个 定制的代理组件,我可以基于这个 Retrieval 组件的架构,为你设计一个类似的组件,满足你的特定需求。

你可以告诉我:

  1. 代理组件的具体用途(比如:请求转发、智能路由、流量控制、负载均衡、身份验证等)。
  2. 需要支持的参数(比如:代理地址、超时时间、认证方式等)。
  3. 核心功能(比如:日志记录、请求缓存、故障恢复等)。
  4. 集成的其他服务(比如:Redis、RabbitMQ、数据库等)。

如果你希望它遵循 ComponentBase 组件框架,我可以按照该模式来实现,让它可以与现有系统无缝对接!

下面的是引用的重要方法上一个代码块里的

retrieval.pybase.py

**kwargs 允许调用 _run() 方法时 传入任意数量的额外参数,即使这些参数没有在方法签名中显式列出#上方代码索引:#### check_decimal_float???
# check():对参数进行验证,确保【相似度阈值】和【权重】为有效的小数,且 top_n 是正数。
    def check(self):
        # "相似性阈值"
        self.check_decimal_float(self.similarity_threshold, "[Retrieval] Similarity threshold")
        # "关键词相似度权重"
        self.check_decimal_float(self.keywords_similarity_weight, "[Retrieval] Keyword similarity weight")
        self.check_positive_number(self.top_n, "[Retrieval] Top N")
---------------------------  ☆ 上方代码块片段 ☆  ---------------------------

# 校验检索相关参数的合法性
# 这是一个 静态方法,用于校验参数 param 是否是 0 到 1 之间的浮点数或整数

'''
校验逻辑:
param 必须是 float 或 int 类型(不允许 str、list 等)。
param 需要在 [0, 1] 之间,否则抛出 ValueError 异常,并在错误信息中包含 descr 描述。
'''
# 这里为什么是0-1 不能超过1? 如果 similarity_threshold = 1.2,会触发 check_decimal_float 的异常抛出,提示超出 [0,1] 范围。而且在前端的拖拉中也只会是0~1之间。因为这些参数代表 归一化(normalized)后的比例或权重,它们的值通常不能超过 1
# 【0表示示完全相同】
    @staticmethod
    def check_decimal_float(param, descr):
        if type(param).__name__ not in ["float", "int"] or param < 0 or param > 1:
            raise ValueError(
                descr
                + " {} not supported, should be a float number in range [0, 1]".format(
                    param
                )
            )
'''
这里为什么要用 @staticmethod?
1. 避免创建对象,提高效率 👉 由于静态方法不依赖实体属性或方法,因此可以直接通过类调用,而不必创建对象
2. 代码组织清晰 👉 适用于逻辑独立的方法,可以让代码更容易读、更结构化
3. 防止修改实例状态 👉 由于静态方法不能访问self,因此不会修改实例的状态,保证了方法的纯函数特征
-------- -------- -------- 结合代码解析 -------- -------- -------- 
4. 这个方法 不访问实例变量,它只 检查参数 是否符合 0~1 之间的范围
5. 适用于 工具方法,属于 数据验证逻辑,不依赖 self
6. 可以直接通过类调用:
Retrieval.check_decimal_float(0.8, "Threshold")  # 正常
Retrieval.check_decimal_float(1.2, "Threshold")  # 抛出 ValueError
'''
==========================================================================
#上方代码索引:##### history, **kwargs???
    def _run(self, history, **kwargs):
        query = self.get_input()
        query = str(query["content"][0]) if "content" in query else ""
        ......
# 讲解重点:【**kwargs】
# [**kwargs] 允许调用 _run() 方法时 传入任意数量的额外参数,即使这些参数没有在方法签名中显式列出 比如:👇
    _run(self, history, user_id=123, debug=True, mode="fast")
# 在 _run() 方法内部,这些参数可以通过 kwargs["user_id"]、kwargs["debug"] 访问,但 当前代码里没有使用 kwargs,所以它只是用来兼容可能的扩展。
'''
目前 **kwargs 没有被使用,它的作用是:

扩展性:可以传递额外的参数而不修改函数签名
兼容性:允许不同调用方传递不同的参数,而 _run() 仍能正常运行
适用场景:未来需要额外参数
目前 **kwargs 只是 占位符,但它提供了未来扩展的可能性
'''
特征 普通方法(self) 静态方法(@staticmethod) 类方法(@classmethod)
依赖实例(self) × ×
依赖类(cls) × ×
访问实例变量 × ×
访问类变量 ×
适用于 需要访问实例 工具方法、验证方法 需要修改类变量

retrieval.py




让我们再来看一个案例

百度翻译baidufanyi.py 路径 → agent/component/baidufanyi.py

# 
import random
# 抽象基类模块 用于定义组件的基类
from abc import ABC
# 用于发送 HTTP 请求,与 百度翻译 API 交互
import requests
# 基础组件类,于构建此组件的父类,定义组件的基本结构和参数。
from agent.component.base import ComponentBase, ComponentParamBase
# 用于生成百度翻译 API 请求签名(加密哈希)
from hashlib import md5

# 百度翻译组件的参数定义类 继承ComponentParamBase
class BaiduFanyiParam(ComponentParamBase):
    """
    Define the BaiduFanyi component parameters.
    """

    def __init__(self):
        super().__init__()
   # appid 和 secret_key 是百度翻译 API 的 身份验证 信息(需要在百度翻译开放平台获取)
        self.appid = "xxx"
        self.secret_key = "xxx"
   # translate:普通翻译   fieldtranslate:专业领域翻译
        self.trans_type = 'translate'
        self.parameters = []
   # source_lang 翻译的源语言   target_lang 翻译的目标语言
        self.source_lang = 'auto'
        self.target_lang = 'auto'
   # domain 如果使用专业领域翻译,需指定领域(如 finance 表示 金融 领域)
        self.domain = 'finance'
# 检查参数合法性
    def check(self):
    # check_empty():确保 appid 和 secret_key 不能为空。
        self.check_empty(self.appid, "BaiduFanyi APPID")
        self.check_empty(self.secret_key, "BaiduFanyi Secret Key")
# check_valid_value():确保 trans_type、source_lang、target_lang、domain 的值在合法选项之内
        self.check_valid_value(self.trans_type, "Translate type", ['translate', 'fieldtranslate'])
# 以下是列举各种语言
        self.check_valid_value(self.source_lang, "Source language",
                               ['auto', 'zh', 'en', 'yue', 'wyw', 'jp', 'kor', 'fra', 'spa', 'th', 'ara', 'ru', 'pt',
                                'de', 'it', 'el', 'nl', 'pl', 'bul', 'est', 'dan', 'fin', 'cs', 'rom', 'slo', 'swe',
                                'hu', 'cht', 'vie'])
        self.check_valid_value(self.target_lang, "Target language",
                               ['auto', 'zh', 'en', 'yue', 'wyw', 'jp', 'kor', 'fra', 'spa', 'th', 'ara', 'ru', 'pt',
                                'de', 'it', 'el', 'nl', 'pl', 'bul', 'est', 'dan', 'fin', 'cs', 'rom', 'slo', 'swe',
                                'hu', 'cht', 'vie'])
# 以下是列举不同专业领域
        self.check_valid_value(self.domain, "Translate field",
                               ['it', 'finance', 'machinery', 'senimed', 'novel', 'academic', 'aerospace', 'wiki',
                                'news', 'law', 'contract'])

# 继承ComponentBase和ABC
class BaiduFanyi(ComponentBase, ABC):
 # 定义组件名称在系统内唯一标识该组件
    component_name = "BaiduFanyi"
    # _run()是组件的 核心执行函数,用于处理翻译请求。
# **kwargs:可以让函数更加灵活,因为它可以接受任意数量的命名参数
# 在函数内部,kwargs 是一个字典,包含了所有传递给函数的额外命名参数。
    def _run(self, history, **kwargs):
'''
self.get_input() 获取输入内容。
如果 content 存在,则用 - 连接多个内容(拼接成单个字符串)。
如果 ans 为空,直接返回 ""
'''
        ans = self.get_input()
        ans = " - ".join(ans["content"]) if "content" in ans else ""
        if not ans:
            return BaiduFanyi.be_output("")

        try:
'''
source_lang 和 target_lang:来源和目标语言。
appid:百度翻译 API 的 应用 ID。
salt:随机数(百度 API 需要此参数)。
secret_key:百度 API 的 密钥
'''
            source_lang = self._param.source_lang
            target_lang = self._param.target_lang
            appid = self._param.appid
            salt = random.randint(32768, 65536)
            secret_key = self._param.secret_key
-------------------------- ☆ 普通翻译API请求 ☆ --------------------------
            if self._param.trans_type == 'translate':
            # md5签名,防止请求被篡改
                sign = md5((appid + ans + salt + secret_key).encode('utf-8')).hexdigest()
     # 发送 HTTP POST 请求 访问 百度翻译 API
                url = 'http://api.fanyi.baidu.com/api/trans/vip/translate?' + 'q=' + ans + '&from=' + source_lang + '&to=' + target_lang + '&appid=' + appid + '&salt=' + salt + '&sign=' + sign
                headers = {"Content-Type": "application/x-www-form-urlencoded"}
            # 解析返回结果
                response = requests.post(url=url, headers=headers).json()

                if response.get('error_code'):
                    BaiduFanyi.be_output("**Error**:" + response['error_msg'])

                return BaiduFanyi.be_output(response['trans_result'][0]['dst'])
-------------------------- ★ 专业翻译API请求 ★ --------------------------
            elif self._param.trans_type == 'fieldtranslate':
                domain = self._param.domain
                sign = md5((appid + ans + salt + domain + secret_key).encode('utf-8')).hexdigest()
                url = 'http://api.fanyi.baidu.com/api/trans/vip/fieldtranslate?' + 'q=' + ans + '&from=' + source_lang + '&to=' + target_lang + '&appid=' + appid + '&salt=' + salt + '&domain=' + domain + '&sign=' + sign
                headers = {"Content-Type": "application/x-www-form-urlencoded"}
                response = requests.post(url=url, headers=headers).json()

                if response.get('error_code'):
                    BaiduFanyi.be_output("**Error**:" + response['error_msg'])

                return BaiduFanyi.be_output(response['trans_result'][0]['dst'])
        # 捕获 所有异常,防止程序崩溃
        except Exception as e:
            BaiduFanyi.be_output("**Error**:" + str(e))

深层次研究

from agent.component.base import ComponentBase, ComponentParamBase

主要用于处理、更新、验证和警告组件参数,适用于那些需要动态配置、递归嵌套结构且要求高可扩展性的系统

class ComponentParamBase(ABC):
    def __init__(self):
        # 初始化输出变量名
        self.output_var_name = "output"
        # 初始化消息历史窗口大小
        self.message_history_window_size = 22
        # 初始化查询参数
        self.query = []
        # 初始化输入参数
        self.inputs = []
        # 初始化调试输入参数
        self.debug_inputs = []
    # 设置组件的名称,并返回当前对象,支持链式调用
    def set_name(self, name: str):
        self._name = name
        return self

    # 抽象方法 子类必须实现 它的目的是检查参数是否有效
    def check(self):
        raise NotImplementedError("Parameter Object should be checked.")

    # 类方法 用于检查类是否具有某个属性 _DEPRECATED_PARAMS 如果没有 则初始化一个空的集合
    @classmethod
    def _get_or_init_deprecated_params_set(cls):
        if not hasattr(cls, _DEPRECATED_PARAMS):
            setattr(cls, _DEPRECATED_PARAMS, set())
        return getattr(cls, _DEPRECATED_PARAMS)
    # 用于检查和初始化实例的已废弃参数集合
    def _get_or_init_feeded_deprecated_params_set(self, conf=None):
        if not hasattr(self, _FEEDED_DEPRECATED_PARAMS):
            if conf is None:
                setattr(self, _FEEDED_DEPRECATED_PARAMS, set())
            else:
                setattr(
                    self,
                    _FEEDED_DEPRECATED_PARAMS,
                    set(conf[_FEEDED_DEPRECATED_PARAMS]),
                )
        return getattr(self, _FEEDED_DEPRECATED_PARAMS)
    # 用于检查和初始化实例的已废弃参数集合
    def _get_or_init_user_feeded_params_set(self, conf=None):
        if not hasattr(self, _USER_FEEDED_PARAMS):
            if conf is None:
                setattr(self, _USER_FEEDED_PARAMS, set())
            else:
                setattr(self, _USER_FEEDED_PARAMS, set(conf[_USER_FEEDED_PARAMS]))
        return getattr(self, _USER_FEEDED_PARAMS)
    # 返回用户提供的参数
    def get_user_feeded(self):
        return self._get_or_init_user_feeded_params_set()
    # 返回已废弃的参数集合
    def get_feeded_deprecated_params(self):
        return self._get_or_init_feeded_deprecated_params_set()
    
    # @装饰器 定义了 _deprecated_params_set 属性 它返回已废弃的参数集合
    @property
    def _deprecated_params_set(self):
        return {name: True for name in self.get_feeded_deprecated_params()}
    
    # 将对象转换为 JSON 字符串
    def __str__(self):
        return json.dumps(self.as_dict(), ensure_ascii=False)

    # 递归地将对象的属性转换成字典 适用于将对象序列化为 JSON 或进行其他操作
    def as_dict(self):
        def _recursive_convert_obj_to_dict(obj):
            ret_dict = {}
            for attr_name in list(obj.__dict__):
                if attr_name in [_FEEDED_DEPRECATED_PARAMS, _DEPRECATED_PARAMS, _USER_FEEDED_PARAMS, _IS_RAW_CONF]:
                    continue
                # get attr
                attr = getattr(obj, attr_name)
                if isinstance(attr, pd.DataFrame):
                    ret_dict[attr_name] = attr.to_dict()
                    continue
                if attr and type(attr).__name__ not in dir(builtins):
                    ret_dict[attr_name] = _recursive_convert_obj_to_dict(attr)
                else:
                    ret_dict[attr_name] = attr

            return ret_dict

        return _recursive_convert_obj_to_dict(self)

    # 用于更新对象的参数,并根据传入的配置进行修改 它处理递归更新参数、检查冗余属性等
    def update(self, conf, allow_redundant=False):
        update_from_raw_conf = conf.get(_IS_RAW_CONF, True)
        if update_from_raw_conf:
            deprecated_params_set = self._get_or_init_deprecated_params_set()
            feeded_deprecated_params_set = (
                self._get_or_init_feeded_deprecated_params_set()
            )
            user_feeded_params_set = self._get_or_init_user_feeded_params_set()
            setattr(self, _IS_RAW_CONF, False)
        else:
            feeded_deprecated_params_set = (
                self._get_or_init_feeded_deprecated_params_set(conf)
            )
            user_feeded_params_set = self._get_or_init_user_feeded_params_set(conf)

        def _recursive_update_param(param, config, depth, prefix):
            if depth > settings.PARAM_MAXDEPTH:
                raise ValueError("Param define nesting too deep!!!, can not parse it")

            inst_variables = param.__dict__
            redundant_attrs = []
            for config_key, config_value in config.items():
                # redundant attr
                if config_key not in inst_variables:
                    if not update_from_raw_conf and config_key.startswith("_"):
                        setattr(param, config_key, config_value)
                    else:
                        setattr(param, config_key, config_value)
                        # redundant_attrs.append(config_key)
                    continue

                full_config_key = f"{prefix}{config_key}"

                if update_from_raw_conf:
                    # add user feeded params
                    user_feeded_params_set.add(full_config_key)

                    # update user feeded deprecated param set
                    if full_config_key in deprecated_params_set:
                        feeded_deprecated_params_set.add(full_config_key)

                # supported attr
                attr = getattr(param, config_key)
                if type(attr).__name__ in dir(builtins) or attr is None:
                    setattr(param, config_key, config_value)

                else:
                    # recursive set obj attr
                    sub_params = _recursive_update_param(
                        attr, config_value, depth + 1, prefix=f"{prefix}{config_key}."
                    )
                    setattr(param, config_key, sub_params)

            if not allow_redundant and redundant_attrs:
                raise ValueError(
                    f"cpn `{getattr(self, '_name', type(self))}` has redundant parameters: `{[redundant_attrs]}`"
                )

            return param

        return _recursive_update_param(param=self, config=conf, depth=0, prefix="")

    # 用于提取所有非内置类型的属性 递归地遍历对象的属性
    def extract_not_builtin(self):
        def _get_not_builtin_types(obj):
            ret_dict = {}
            for variable in obj.__dict__:
                attr = getattr(obj, variable)
                if attr and type(attr).__name__ not in dir(builtins):
                    ret_dict[variable] = _get_not_builtin_types(attr)

            return ret_dict

        return _get_not_builtin_types(self)
    
    # validate 方法用于验证对象的参数是否符合预定义的规则 规则存储在 JSON 文件中
    def validate(self):
        self.builtin_types = dir(builtins)
        self.func = {
            "ge": self._greater_equal_than,
            "le": self._less_equal_than,
            "in": self._in,
            "not_in": self._not_in,
            "range": self._range,
        }
        home_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
        param_validation_path_prefix = home_dir + "/param_validation/"

        param_name = type(self).__name__
        param_validation_path = "/".join(
            [param_validation_path_prefix, param_name + ".json"]
        )

        validation_json = None

        try:
            with open(param_validation_path, "r") as fin:
                validation_json = json.loads(fin.read())
        except BaseException:
            return

        self._validate_param(self, validation_json)

    def _validate_param(self, param_obj, validation_json):
        default_section = type(param_obj).__name__
        var_list = param_obj.__dict__

        for variable in var_list:
            attr = getattr(param_obj, variable)

            if type(attr).__name__ in self.builtin_types or attr is None:
                if variable not in validation_json:
                    continue

                validation_dict = validation_json[default_section][variable]
                value = getattr(param_obj, variable)
                value_legal = False

                for op_type in validation_dict:
                    if self.func[op_type](value, validation_dict[op_type]):
                        value_legal = True
                        break

                if not value_legal:
                    raise ValueError(
                        "Plase check runtime conf, {} = {} does not match user-parameter restriction".format(
                            variable, value
                        )
                    )

            elif variable in validation_json:
                self._validate_param(attr, validation_json)

    # 用于验证参数的类型和范围 确保符合预期          
    @staticmethod
    def check_string(param, descr):
        if type(param).__name__ not in ["str"]:
            raise ValueError(
                descr + " {} not supported, should be string type".format(param)
            )

    @staticmethod
    def check_empty(param, descr):
        if not param:
            raise ValueError(
                descr + " does not support empty value."
            )

    @staticmethod
    def check_positive_integer(param, descr):
        if type(param).__name__ not in ["int", "long"] or param <= 0:
            raise ValueError(
                descr + " {} not supported, should be positive integer".format(param)
            )

    @staticmethod
    def check_positive_number(param, descr):
        if type(param).__name__ not in ["float", "int", "long"] or param <= 0:
            raise ValueError(
                descr + " {} not supported, should be positive numeric".format(param)
            )

    @staticmethod
    def check_nonnegative_number(param, descr):
        if type(param).__name__ not in ["float", "int", "long"] or param < 0:
            raise ValueError(
                descr
                + " {} not supported, should be non-negative numeric".format(param)
            )

    @staticmethod
    def check_decimal_float(param, descr):
        if type(param).__name__ not in ["float", "int"] or param < 0 or param > 1:
            raise ValueError(
                descr
                + " {} not supported, should be a float number in range [0, 1]".format(
                    param
                )
            )

    @staticmethod
    def check_boolean(param, descr):
        if type(param).__name__ != "bool":
            raise ValueError(
                descr + " {} not supported, should be bool type".format(param)
            )

    @staticmethod
    def check_open_unit_interval(param, descr):
        if type(param).__name__ not in ["float"] or param <= 0 or param >= 1:
            raise ValueError(
                descr + " should be a numeric number between 0 and 1 exclusively"
            )

    @staticmethod
    def check_valid_value(param, descr, valid_values):
        if param not in valid_values:
            raise ValueError(
                descr
                + " {} is not supported, it should be in {}".format(param, valid_values)
            )

    @staticmethod
    def check_defined_type(param, descr, types):
        if type(param).__name__ not in types:
            raise ValueError(
                descr + " {} not supported, should be one of {}".format(param, types)
            )

    @staticmethod
    def check_and_change_lower(param, valid_list, descr=""):
        if type(param).__name__ != "str":
            raise ValueError(
                descr
                + " {} not supported, should be one of {}".format(param, valid_list)
            )

        lower_param = param.lower()
        if lower_param in valid_list:
            return lower_param
        else:
            raise ValueError(
                descr
                + " {} not supported, should be one of {}".format(param, valid_list)
            )

    @staticmethod
    def _greater_equal_than(value, limit):
        return value >= limit - settings.FLOAT_ZERO

    @staticmethod
    def _less_equal_than(value, limit):
        return value <= limit + settings.FLOAT_ZERO

    @staticmethod
    def _range(value, ranges):
        in_range = False
        for left_limit, right_limit in ranges:
            if (
                    left_limit - settings.FLOAT_ZERO
                    <= value
                    <= right_limit + settings.FLOAT_ZERO
            ):
                in_range = True
                break

        return in_range

    @staticmethod
    def _in(value, right_value_list):
        return value in right_value_list

    @staticmethod
    def _not_in(value, wrong_value_list):
        return value not in wrong_value_list

    def _warn_deprecated_param(self, param_name, descr):
        if self._deprecated_params_set.get(param_name):
            logging.warning(
                f"{descr} {param_name} is deprecated and ignored in this version."
            )

    def _warn_to_deprecate_param(self, param_name, descr, new_param):
        if self._deprecated_params_set.get(param_name):
            logging.warning(
                f"{descr} {param_name} will be deprecated in future release; "
                f"please use {new_param} instead."
            )
            return True
        return False
这里运用到json 为什么要用json?
  • 方便序列化json.dumps()将对象转换为JSON字符串,可以轻松地保存或传输对象数据。
  • 方便调试和展示:在 __str__ 方法中返回 JSON 字符串,可以方便地查看对象的内容。尤其是在调试过程中,直接打印出对象的 JSON 格式可以帮助开发人员快速查看对象的状态及其内部数据结构
  • 与大模型交互:当涉及到与大语言模型(如 GPT)或其他机器学习模型的交互时,JSON 格式的数据通常是标准的输入和输出格式。将对象转为 JSON 字符串,能够更容易地将数据传递给模型进行处理或分析,模型通常会接受 JSON 格式的数据进行训练或推理。
如何去运用?
  • 数据交换:可以将这个 JSON 字符串用作 API 请求或响应的数据格式。例如,你可以将这个对象作为 HTTP 请求的 body 发送,或者从网络中获取 JSON 格式的数据,然后解析回对象。
  • 持久化存储:如果你需要将对象数据持久化到数据库或文件系统,JSON 是一个很好的存储格式。例如,将对象数据存储在文件中或数据库表的 JSON 类型字段中,便于未来读取和操作。
  • 配置和参数更新update 方法中接收到的 conf 参数实际上是一个字典对象,它通过递归的方式更新对象的属性。如果你将对象序列化为 JSON 格式后,可以将 JSON 作为配置文件传递给应用,应用根据该配置动态调整其行为。这使得系统更加灵活和可配置。
  • 验证与校验validate 方法利用存储在 JSON 文件中的规则对对象进行参数验证,这是一种常见的方式来确保输入的数据符合特定的格式或限制。可以通过动态加载配置文件来验证对象的数据,保证数据的合法性。

怎么和大模型交互 json是以什么规范 是否让机器更好的理解?

如果自己开发了一个代理组件后如何去融入呢?

index.tsx

路径web/src/pages/flow/form/baidu-fanyi-form/index.tsx

BaiduFanyiForm 是一个用于配置百度翻译(Baidu Fanyi)接口参数的表单组件。该组件基于 Ant Design (antd) 的 Form 组件,允许用户输入翻译所需的 AppID、密钥、翻译类型、领域、源语言和目标语言

// useTranslate('flow'):自定义 Hook,提供国际化翻译能力
import { useTranslate } from '@/hooks/common-hooks';
// Form, Input, Select:Ant Design 表单组件
import { Form, Input, Select } from 'antd';
// useMemo:优化计算、提高性能,避免不必要的重新计算
import { useMemo } from 'react';
// 从 constant 中引入翻译领域和语言选项
import {
  BaiduFanyiDomainOptions,
  BaiduFanyiSourceLangOptions,
} from '../../constant';
// 表单组件的类型定义,包含 onValuesChange, form, node 等参数
import { IOperatorForm } from '../../interface';
// 动态输入组件,可能用于变量替换
import DynamicInputVariable from '../components/dynamic-input-variable';

/*
生成 trans_type(翻译类型)的 Select 选项:
translate:普通翻译
fieldtranslate:专业领域翻译
选项的 label 通过 t() 进行国际化翻译。
*/
const BaiduFanyiForm = ({ onValuesChange, form, node }: IOperatorForm) => {
  const { t } = useTranslate('flow');
  const options = useMemo(() => {
    return ['translate', 'fieldtranslate'].map((x) => ({
      value: x,
      label: t(`baiduSecretKeyOptions.${x}`),
    }));
  }, [t]);
// 生成 领域翻译 选项(如医学、法律等)
  const baiduFanyiOptions = useMemo(() => {
    return BaiduFanyiDomainOptions.map((x) => ({
      value: x,
      label: t(`baiduDomainOptions.${x}`),
    }));
  }, [t]);
// 源语言(source_lang)和 目标语言(target_lang)
  const baiduFanyiSourceLangOptions = useMemo(() => {
    return BaiduFanyiSourceLangOptions.map((x) => ({
      value: x,
      label: t(`baiduSourceLangOptions.${x}`),
    }));
  }, [t]);

  return (
    <Form
      name="basic" //表单名称
      autoComplete="off" //关闭自动填充
      form={form} //绑定 Ant Design 表单实例,允许动态设置表单值
      onValuesChange={onValuesChange} //表单值变更时触发外部回调
      layout={'vertical'}
    >
          <!-- 允许用户输入 AppID 和 密钥,用于身份验证 -->
      <DynamicInputVariable node={node}></DynamicInputVariable>
      <Form.Item label={t('appid')} name={'appid'}>
        <Input></Input>
      </Form.Item>
      <Form.Item label={t('secretKey')} name={'secret_key'}>
        <Input></Input>
      </Form.Item>
          <!-- 翻译类型 -->
      <Form.Item label={t('transType')} name={'trans_type'}>
        <Select options={options}></Select>
      </Form.Item>
          <!-- 领域翻译 仅在 fieldtranslate 选中时显示 -->
  <!-- dependencies={['model_type']}:监听 model_type 字段的变化 -->
      <Form.Item noStyle dependencies={['model_type']}>
        {({ getFieldValue }) =>
          getFieldValue('trans_type') === 'fieldtranslate' && (
            <Form.Item label={t('domain')} name={'domain'}>
              <Select options={baiduFanyiOptions}></Select>
            </Form.Item>
          )
        }
      </Form.Item>
      <Form.Item label={t('sourceLang')} name={'source_lang'}>
        <Select options={baiduFanyiSourceLangOptions}></Select>
      </Form.Item>
      <Form.Item label={t('targetLang')} name={'target_lang'}>
        <Select options={baiduFanyiSourceLangOptions}></Select>
      </Form.Item>
    </Form>
  );
};
export default BaiduFanyiForm;

BaiduFanyiForm 组件是百度翻译 API 参数的前端配置表单,支持动态交互(如 trans_type 选择 fieldtranslate 时动态显示 domain 选项)。该组件用于某个流程(flow)系统,允许用户输入翻译 API 相关信息,并提供 国际化支持useTranslate()



_ _ init _ _.py

首先在 agent/component/__init__.py 在组件同级目录下有一个__init__.py
它的主要作用是**导入并管理各种组件,这些组件用于数据处理、信息检索、翻译、SQL执行、财经数据获取等**不同功能。其中的重要代码:

# import importlib:用于动态导入模块(在 component_class 方法中使用)
# 这些组件分布在 agent.component 模块下,提供不同的功能

'''
该函数用于动态加载组件,通过 importlib.import_module("agent.component") 导入 agent.component 模块,然后使用 getattr(m, class_name) 获取类,从而返回对应的组件类。
'''
def component_class(class_name):
    m = importlib.import_module("agent.component")
    c = getattr(m, class_name)
    return c

'''
__all__ 列表:指定模块对外暴露的所有组件
使其成为 from module import * 时可以被访问的对象。
'''
__all__ = [
    "GitHub",
    "GitHubParam",
    "BaiduFanyi",
    "BaiduFanyiParam",
]

web/src/pages/flow/flow-drawer/index.tsx

web/src/pages/flow/flow-drawer/index.tsx里有这样一行代码
import BaiduFanyiForm from '../form/baidu-fanyi-form';
该文件 index.tsx 主要用于**表单抽屉组件 (FormDrawer)**,用于在 UI 中展示不同操作节点的表单,并支持编辑、调试等功能。

代码功能概览

🔹 动态渲染不同的表单组件BaiduFanyiForm 等)
🔹 支持不同类型的操作节点(Operator)(如 BaiduFanyi, Google, Generate 等)
🔹 提供表单输入交互,并支持单步调试
🔹 使用 Drawer 组件作为侧边栏弹出窗口

★ 组件导入

import { Drawer, Flex, Form, Input } from 'antd';
import { get, isPlainObject, lowerFirst } from 'lodash';
import { Play } from 'lucide-react';
import { CloseOutlined } from '@ant-design/icons';
...

📌 作用:引入 Ant Design 组件(DrawerFormInput)、lodash 工具库以及 Play(调试按钮)、CloseOutlined(关闭按钮)。

★ 引入表单组件

# 当然不能忘了我们的百度翻译
import BaiduFanyiForm from '../form/baidu-fanyi-form';
import GoogleForm from '../form/google-form';
import GenerateForm from '../form/generate-form';
import RetrievalForm from '../form/retrieval-form';
...

📌 作用:引入 BaiduFanyiForm 组件,该组件用于百度翻译的表单交互。

✅ 这些表单组件用于不同的 Operator 操作,例如:

  • BaiduFanyiForm百度翻译表单
  • GoogleFormGoogle 搜索表单
  • GenerateForm文本生成表单
  • RetrievalForm信息检索表单

★ FormMap(操作类型与表6单组件的映射)

const FormMap = {
  [Operator.BaiduFanyi]: BaiduFanyiForm,
  [Operator.Google]: GoogleForm,
  [Operator.Generate]: GenerateForm,
  [Operator.Retrieval]: RetrievalForm,
};

📌 作用
动态匹配 Operator(操作类型)与 对应表单组件
OperatorBaiduFanyi 时,渲染 BaiduFanyiForm
可以扩展不同的表单类型

☆ FormDrawer组件

const FormDrawer = ({ visible, hideModal, node, singleDebugDrawerVisible, hideSingleDebugDrawer, showSingleDebugDrawer }: IModalProps<any> & IProps) => {

📌 作用FormDrawer 是一个 表单抽屉组件,用于展示不同类型的表单。

☆ 动态渲染表单

const OperatorForm = FormMap[operatorName] ?? EmptyContent;

📌 作用: ✅ 根据 Operator(操作类型)选择对应的表单组件
✅ 如果 Operator 不在 FormMap 中,则显示 EmptyContent(空组件)

☆ 处理表单数据

useEffect(() => {
  if (visible) {
    form.resetFields();
    form.setFieldsValue(node?.data?.form);
  }
}, [visible, form, node?.data?.form]);

📌 作用: ✅ 当抽屉可见时,重置表单并填充数据
如果 OperatorCategorize,则特殊处理分类数据

☆ 侧边栏 (Drawer)

<Drawer
  title={
    <Flex>
      <OperatorIcon name={operatorName} />
      <Input value={name} onChange={handleNameChange} />
      <Play onClick={showSingleDebugDrawer} />
      <CloseOutlined onClick={hideModal} />
    </Flex>
  }
  open={visible}
>
  <OperatorForm onValuesChange={handleValuesChange} form={form} node={node} />
</Drawer>

📌 作用: ✅ 标题区域包含操作图标、输入框、调试按钮、关闭按钮
使用 OperatorForm 组件渲染表单
点击 Play 按钮打开调试模式


★ ★ 总结一下BaiduFanyiForm相关逻辑 ★ ★
index.tsx 里,BaiduFanyiForm 被注册到 FormMap,然后通过 FormDrawer 组件

🚀 如何渲染 BaiduFanyiForm

  1. Operator.BaiduFanyi 触发 BaiduFanyiForm
  2. FormDrawer 组件动态匹配 FormMap[Operator.BaiduFanyi]
  3. BaiduFanyiForm 渲染在 Drawer

📌 结论: ✅ BaiduFanyiForm 只是众多表单组件之一,专门用于百度翻译的功能
index.tsx 主要是表单的动态加载器,统一管理所有 Operator 类型
FormDrawer 负责 UI 渲染、数据填充、调试交互等


agent/component/baidufanyi.py 【后端代码】
agent/component/_ _ init _ _.py 【初始化】
③ web/src/pages/flow/flow-drawer/index.tsx【表单抽屉组件 UI展示】
④ web/src/pages/flow/form/baidu-fanyi-form/index.tsx 【提供用户界面让用户能配置并提交参数】
⑤ web/src/pages/flow/constant.tsx 【定义一个应用程序中使用的各种图标、常量、枚举、接口、状态和函数】
⑥ web/src/locales/zh.ts 【把所有要显示的搞到zh.ts中(前端组件描述)】
⑦ web/src/pages/agent/constant.tsx【引入组件所需的各种图标、以及操作项的样式、初始值、语言等】
⑧ web/src/pages/agent/form-sheet/use-form-config-map.tsx 【表单配置映射、配置不同操作对应表单组件】
⑨ web/src/pages/agent/form/baidu-fanyi-form/index.tsx 【构建配置表单 】
⑩ web/src/pages/agent/hooks.tsx 【管理图形界面流程图 各种钩子 → 建数据处理流程】